package com.modbus.tcp.client;


import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;
import com.serotonin.modbus4j.msg.ReadHoldingRegistersResponse;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;


/**
 * ServerEndpoint
 * <p>
 * 使用springboot的唯一区别是要@Component声明下，而使用独立容器是由容器自己管理websocket的，但在springboot中连容器都是spring管理的。
 * <p>
 * 虽然@Component默认是单例模式的，但springboot还是会为每个websocket连接初始化一个bean，所以可以用一个静态set保存起来。
 *
 * @author sam
 * @since 2017/9/13
 */
@ServerEndpoint("/rtuTcp/{slaveId}/{address}/{quantity}") //WebSocket客户端建立连接的地址
@Component
@Data
public class RtuTcpServerEndpoint {


    @Value("${modbus-tcp.host}")
    private String host;

    @Value("${modbus-tcp.port}")
    private int port;

    private static ModbusMaster master;
    private static ReadHoldingRegistersRequest request;

    private boolean enable = true;


    @PostConstruct
    public void initModbusMaster() throws UnknownHostException {
        ModbusFactory modbusFactory = new ModbusFactory();
        IpParameters params = new IpParameters();
        params.setHost(host);
        params.setPort(port);
        params.setEncapsulated(true);
        master = modbusFactory.createTcpMaster(params, false);// TCP 协议
        try {
            //设置超时时间
            master.setTimeout(1000);
            //设置重连次数
            master.setRetries(3);
            //初始化
            master.init();
        } catch (ModbusInitException e) {
            e.printStackTrace();
        }
    }

    /**
     * 建立连接的回调方法
     *
     * @param session 与客户端的WebSocket连接会话
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("slaveId") Integer slaveId, @PathParam("address") Integer address, @PathParam("quantity") Integer quantity) {
        try {
            request = new ReadHoldingRegistersRequest(slaveId, address, quantity);

            sendMessage(session, "modbus 已连接");
        } catch (ModbusTransportException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 收到客户端消息的回调方法
     *
     * @param message 客户端传过来的消息
     * @param session 对应的session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        while (enable) {
            try {
                ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request);
                short[] shortData = response.getShortData();

                // 控制台输出
                for (int value : shortData) {
                    sendMessage(session, value);
                }

            } catch (ModbusTransportException e) {
                throw new RuntimeException(e);
            }
        }
    }


    /**
     * 发生错误的回调方法
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        destroy();
    }


    /**
     * 关闭连接的回调方法
     */
    @OnClose
    public void onClose(Session session) {
        destroy();
    }


    /**
     * 单独发送消息
     *
     * @param session
     * @param message
     */
    public void sendMessage(Session session, Object message) {
        try {
            RemoteEndpoint.Basic basicRemote = session.getBasicRemote();
            if (message instanceof String) {
                basicRemote.sendText((String) message);
            } else if (message instanceof ByteBuffer) {
                basicRemote.sendBinary((ByteBuffer) message);
            } else {
                basicRemote.sendObject(message);
            }
        } catch (IOException | EncodeException e) {
            e.printStackTrace();
        }
    }


    private void destroy() {
        enable = false;
        master.destroy();
    }

}
